﻿
// c++ 基础 以及类的基础部分

//#include <iostream>
//using namespace std; // 将这个命名空间全部展开
//// using 这个是针对命名空间的 , 类中不存在这个概念
////复习 const 引用
////  const 看其在谁的旁边 , 在谁的旁边就修饰谁
//
////int main()
////{
////	// 此时是 : 只可写 , 不可读的效果
////	int* const p = nullptr;
////	// const 修饰指针变量本身 , 指针变量本身就不可改变
////	int a = 1;
////	// p = &a; / err
////	*p = 2; // ok - > 写入
////}
//
////// const 修饰
////int main()
////{
////	//const 修饰指针变量指向的内容 , 即: 只可读 , 不可写
////	const int* p = nullptr;
////	int a = 1;
////	p = &a; // ok - > 读
////	// *p = 2; // err - > 写 err
////}
//
////  命名空间 namespace 只能定义在全局中
////项目中有多个命名空间会自动合并
//
////命名空间
//
////  1 . 正常定义
//namespace GJG
//{
//	int a = 1;
//	int b = 2;
//
//	int ADD(int a, int b)
//	{
//		return a + b;
//	}
//
//	char c[] = "GJG 的命名空间";
//}
//
////2. 命名空间的嵌套
//namespace Compay
//{
//	// GJG的加法函数
//	namespace GJG
//	{
//		int ADD(int a, int b)
//		{
//			return a + b;
//		}
//	}
//
//	//Other People 加法函数
//	 // 不同域中可以定义同名函数
//	namespace OtherPeople
//	{
//		int ADD(int a, int b)
//		{
//			return a + b;
//		}
//	}
//}
//
////正常定义
//void Test01()
//{
//	cout << "GJG :: a = " << GJG::a << endl;
//	cout << "GJG :: b = " << GJG::b << endl;
//	cout << "GJG :: ADD(1,2) = " << GJG::ADD(1, 2) << endl;
//	cout << "GJG :: c = " << GJG::c << endl;
//
//}
//
////命名空间的嵌套
//void Test02()
//{
//	cout << "Compay::GJG::ADD(1, 2) = " << Compay::GJG::ADD(1, 2) << endl;
//	cout << "Compay::OtherPeople::ADD(1, 2) = " << Compay::OtherPeople::ADD(1, 2) << endl;
//}
//
/////////////////////////////  这里记住重要也一点 , 不同文件中的同一命名空间可以自动合并 ///////////////////
//#include "Stack.h"
//void Test03()
//{
//	int a = 1;
//	int b = 2;
//	cout << "a  b 交换前 : a  b"<<endl << a << " " << b << endl;
//	GJG::Swap(&a, &b);
//	cout << "a  b 交换后 : a  b" << endl << a << " " << b << endl;
//
//}
//int main()
//{
//	//正常定义
//	//Test01();
//	//命名空间的嵌套
//	//Test02();
//	//定义在不同文件中的同名命名空间会自动合并
//	Test03();
//
//	return 0;
//}



////////////////// 命名空间的使用

#include <iostream>

// 1. 命名空间全部展开 - > 项目中不推荐
// 2. 指定域使用 - > 常用的
// 3. 展开常用的 


////1. 命名空间全部展开, 整个项目中都可以使用, 但会有风险
//
//// 命名空间的展开用 using 关键字
//using namespace std; // 将 std 这个命名空间全部展开 - > 平时练习题目时可以使用
//// 为什么要展开才能使用 ?
//// 1. cout 是在 std 这个命名空间中定义的 , 所以要不展开只能指定~
//// 2. 必须包含头文件 #include <iostream> -  > 因为 std 这个命名空间是包含在这个头文件中的
//
//int main()
//{
//	cout << "HELLO WORLD ! " << endl;
//	return 0;
//}


// 2. 指定
//
//namespace GJG
//{
//	int a = 1;
//	int b = 2;
//
//}

//将 GJG 这个命名空间中的 a 改为 5 , 并打印;


// 3. 展开常用的
//namespace GJG
//{
//	int a = 1;
//	int b = 2;	
//}
//
//
////打印 10 次 a , 1 次 b
//using GJG::a;
//using std::cout;
//using std::endl;
//int main()
//{
//	//std::cout << "HELLO WORLD !" << std::endl;
//	//GJG::a = 5;
//	//std::cout << "a = " << GJG::a <<std::endl;
//
//	//打印10 次 a , 1 次 b
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << a << endl;
//	cout << GJG::b << endl;
//
//	return 0;
//
//}

//////////////////////////////// CPP 的输入 , 输出

// c++ 中输入 : cin >> 
// 输出 : cout , 还有io流的概念放在后面章节讲解
// 都是在 std 中
#include <iostream>
//输入输出可以自动识别类型 

//int main()
//{
//	int a = 0;
//	double b = 0.0;
//	char c = '0';
//
//	std::cin >> a;
//	std::cin >> b;
//	std::cin >> c;
//
//	std::cout << "int : a = " << a <<std::endl<<"double : b = " << b <<std::endl << "char : c = " << c << std::endl;
//	return 0;
//}

////////////////////////////// 缺省参数 /////////////

//缺省参数 : 是指 -> 给函数一个缺省值 ,当调用函数时,实参部分没有给初始值那么就会用缺省值 ,若给了就用实参值
// 分为2种 : 1. 全缺省  2. 半缺省

///////////////////////////////////////  这里特别注意:cpp规定: 缺省参数必须从右到左依次缺省 , 实参部分必须从左到右依次传参
///////////////////////////////////////                      不得跳跃缺省或传参

using namespace std;
//// 1. 全缺省 
//void Fun1(int a = 0, int b = 0 , int c = 0)
//{
//	cout << a << " " << b << " " << c << endl;
//}
//
//// 2. 半缺省 
//void Fun2(int a , int b = 0, int c = 0)
//{
//	cout << a << " " << b << " " << c << endl;
//}
//
//int main()
//{
//	//全缺省
//	cout << "全缺省 >: " << endl;
//	Fun1();
//	Fun1(1);
//	Fun1(1,2);
//	Fun1(1,2,3);
//
//	//半缺省
//	cout << "半缺省 >: " << endl;
//	Fun2(1);
//	Fun2(1,2);
//	Fun2(1,2,3);
//	return 0;
//}


////////////////////////////////////////// 函数重载 ///////

//函数构成重载的条件 : 形参部分不同 
//不同体现在以下方面 , 将逐一解释
// 函数重载 : 函数名可以相同

//1. 形参部分参数的类型不同 2. 形参部分参数的参数个数不同 3. 形参部分参数部分的顺序不同(属于类型的一部分)
// 注意这里构成重载与函数返回值无关 !!!

////1. 形参部分参数的类型不同 , 构成重载
//int ADD(int a , int b)
//{
//	return a + b;
//}
//
//double ADD(double a, double b)
//{
//	return a + b;
//}
//
////2. 形参部分参数个数与上面不同 , 与上面构成重载
//int ADD(int a , int b , int c)
//{
//	return a + b + c;
//}
//
////3. 顺序不同
//double ADD(int a , double b)
//{
//	return a + b;
//}
//
//double ADD(double a, int b)
//{
//	return a + b;
//}

//int main()
//{
//
//	cout << ADD(1, 2) << endl;
//	cout << ADD(1.1, 3.0) << endl;
//	cout << ADD(1, 2, 3) << endl;
//	cout << ADD(1,2.2) << endl;
//	cout << ADD(2.2,1) << endl;
//}


//////////////////////////////////////////////  重点区分一个东西 /////////////////////////////////////

//////////////////////////////   判断以下两个函数是否重载 , 会发生什么情况
//
//
//void Fun(int a = 0 , int b = 0)
//{
//	cout << "int a = 0 , int b = 0" << endl;
//}
//
//void Fun()
//{
//	cout << " " << endl;
//}
//
////以上两个函数是构成函数重载的 , 但在调用时会有歧义
//// 因为两个函数都可以不传实参, 编译器不知道调用哪个
//
////  故 : 全缺省函数  和 无参函数 有且只有一个存在
// 
//
//int main()
//{
//
//	Fun(); // 报错 : 对重载函数调用不明确
//	return 0;
//}



/////////////////////////////////////  引用  /////////////

// 引用 : 就是起别名 , 特别有用 , 它是不开辟空间的

//引用的特性: 
// 1. 引用使用必须初始化 (也就是必须知道引用的是谁)
// 2. 一个变量可以有多个引用
// 3. 引用一旦引用了一个实体就不能在引用其它实体
// 4. 引用的底层是指针 (理解引用就是起别名)
//int main()
//{
//	int a = 1;
//	int& b = a; // b 是 a 的引用(别名)
//	int& c = b;
//	cout << "引用是不开辟空间的 , 如下 :" << endl;
//
//	cout << &a << endl;
//	cout << &b << endl;
//	cout << &c << endl;
//	c++;
//	int& d = a;
//	cout << "一个变量可以有多个别名:" << endl;
//
//	cout << a << endl;
//	cout << b << endl;
//	cout << c << endl;
//	cout << &d << endl;
//	// b , c , d 均是对 a 的引用 
//
//	//引用了一个实体就不能引用其它实体
//
//	int num = 2;
//	b = num; // 这里不再是引用了 ,而是赋值
//	b++;
//	cout << "b 之前是对 a 的引用 , 所以不能引用其它实体了(例如 b++ 不能改变 num 的值)" << endl;
//
//	cout << b << endl;
//	cout << num << endl;
//
//	return 0;
//}

// 引用的实践

// 1 . 交换两个值

//void Swap(int& ra, int& rb)
//{
//	int tmp = ra;
//	ra = rb;
//	rb = tmp; 
//}
//
//int main()
//{
//	int a = 1;
//	int b = 2;
//	Swap(a, b);
//	cout << a << " " << b << endl;
//	return 0;
//}

// 2. 列如栈的初始化 

//typedef int STDataType;
//typedef struct Stack
//{
//	STDataType* a;
//	int top;
//	int capacity;
//}ST;
//
//// 这里引用就不需要传地址了, rs 就是定义的栈的别名 , 给了缺省值就很方便开数组了 , 不传值就默认开4个大小的数组往后扩容
//
//void STInit(ST& rs, int n = 4)
//{
//	rs.a = (STDataType*)malloc(n * sizeof(STDataType));
//	rs.top = 0;
//
//	rs.capacity = n;
//}

//////////////////////////////////////  const 引用

//涉及: 权限知识 
// 可以引用 const 对象 , 也可以引用普通对象

// 权限放大 , 缩小 
//权限不可以放大, 但是可以缩小
//int main()
//{
//	int a = 1;
//	int* const p = &a;
//	// const 修饰 指针变量 p , p的指向不能改变 - > 只可写 , 不可读
//	const int b = 2; // 只可读 , 不可写
//	//对 b 起别名
//
//	 //int& c = b; // err - > 这里对 b 的权限进行放大了 - > 成了可读可写的了
//	const int& c = b; // ok
//
//	int d = 3; // d 可以读 , 可以被修改
//	d = 4; // ok
//	
//	// 权限缩小
//	int& const n = d;
//	const int& m = d;
// 
//	 double num = 12.34; 
//	 //编译报错：“初始化”: ⽆法从“double”转换为“int &”
//	 // 这里涉及隐士类型转换 , d 会放到临时空间 ,在放入 rd 临时空间具有常性 , 内容不能被修改,
//	 const int& rd = num;
//	
//	return 0;
//}


////////////////////////////// 存在临时空间的情况 , 临时空间具有常性  

// 一般都是以下几种

// 1. 赋值  2. 隐士类型转换   3. 函数传参 , 传值返回

///////////////////////////// 引用和指针的关系对比 


// 1. 引用是变量起别名 , 别名的变化就是变量本身的变化  , 不需要解引用
// 2. 引用和指针在底层都是一样的 , 都是地址
// 3. 引用在使用时必须初始化 , 指针没有要求
// 4. 引用是不单独开辟空间的  ,而 指针是要开辟空间的
// 5. 引用在引用一个实体后就不能引用其它实体 , 指针可以改变指向的对象
// 6. 指针存在空指针 , 野指针的情况 , 而引用相对更加安全 
// 7. sizeof 的大小不一样 , 引用sizeof就是计算的是被引用的本身大小 , 而指针则是看在平台环境(32位 - > 4个字节 , 64位 -> 8个字节)


////////////////////////////////////////////  CPP 中的内联函数 inline 

// 内联函数是替代 c 语言中的宏的问题 
// C 语言中的宏函数是不能进行调试的 , 而 inline 可以
// c++ 会在调用的地方展开 inline 函数 , 这样不需要开辟栈帧 ,提高效率
// inline 函数适用函数短小 , 频繁调用时就会展开 - > 但取决于编译器
//inline int Add(int x, int y)
//{
//	int ret = x + y;
//	ret += 1;
//	ret += 1;
//	ret += 1;
//	return ret;
//} 
//int main()
//{
//	// 可以通过汇编观察程序是否展开
//	// 有call Add语句就是没有展开，没有就是展开了
//	int ret = Add(1, 2);
//	cout << Add(1, 2) * 5 << endl;
//	return 0;
//}


///////////////////////////////////////  重要的面试问题 /////////////////////////////////////

// 写一个 ADD 的宏函数 

//#define ADD(a , b) ((a)+(b)) 
// 宏函数在调用时就会替换 后面的内容

// 1 . 为什么不能加分号? 
// 2 . 为什么要加外⾯的括号?
// 3 . 为什么要加⾥⾯的括号?


// 1. 可能有 打印 ADD 返回值的情况
//  cout << ADD(1,2) << endl;
// 替换成 cout <<  ((1)+(2)) << endl;
// 2. 可能有 
// cout << ADD(1,2) * 5 << endl;
// 要计算的是 ADD(1,2) 返回值 * 5 所以外面要加括号

//int main()
//{
//	int ret = ADD(1, 2); // ((1) + (2)) 
//	cout << ADD(1, 2) << endl;
//	//若加了分号就不对了
//	cout << ADD(1, 2) * 5 << endl;
//	int x = 1, y = 2;
//	ADD(x & y, x | y); // -> (x&y+x|y)
//	// 里面不加括号就不对了 , & | 优先级比 + 低 , 所以里面要加括号
//	return 0;
//}


//////////////////////////////////////// 类的定义
// 关键子 class
// 类的定义与 C 语言结构体相似 , 但类中可以存函数 , 结构体只可以存数据

//类访问限定符 : public ,  private , protect 

//定义一个日期类 , 
//class Date
//{
//public:
//	//成员函数
//	//void init( date* const this , int year = 0 , int month = 0 , int day = 0)
//	void Init(int year = 0 , int month = 0 , int day = 0)
//	{
//		this->_year = year; // ok 
//		_month = month; // ok 
//		_day = day;
//	}
//
//
//	void Print()
//	{
//		cout << _year << '/' << _month << '/' << _day << endl;
//	}
//
//private:
//	// 这里是声明 !!!!!!!!!!!!!!!!!
//	//一般类的成员变量放到私有中
//	//一般为了区分对成员变量都有特殊标识
//
//	//成员变量 , 这里是声明 , 没有开辟空间
//	int _year;
//	int _month;
//	int _day;
//};
//
//
//////////// 在 C++ 中 struct 也可以定义类
//// class 类中默认成员是私有的 , 但 struct 类中默认成员是公有的 
//
//// c ++ 中加了  this 指针, 类的成员函数中会有一个参数是 this 指针 , 是在函数的第一个位置
//// 规定: 不能显示传 this 指针 , 但在类中可以使用 this 指针
//
//
//int main()
//{
//	Date d1; // 类的实例化 , d1 就是类 Date 的对象
//	// d1.Init(&d1,2024.10.14) - > 编译后就会成这样 , 会自动在第一个参数位置增加指针 
//	d1.Init(2024,10,14);
//	d1.Print();
//
//	Date d2;
//	d2.Init(2024);
//	d2.Print();
//
//	return 0;
//}


//////////  对象大小 , 计算方式还是内存对齐原则

// 首先 : 对象中只存类的成员变量 , 不存类的成员函数, 也就是说函数指针不存在对象中
// 因为 : 函数都是同一个函数,存在在一个公共区域都可以使用 , 没必要存在对象中 , 同一函数访问是有 this 指针访问不同的对象 
// 而成员变量是不同的 ,要存在不同对象中


// 内存对齐原则
// 第一个成员变量在偏移量位置为 0 处
//其他成员变量要对⻬到某个数字（对⻬数）的整数倍的地址处。
 //注意：对⻬数 = 编译器默认的⼀个对⻬数与该成员⼤⼩的较⼩值。
 //VS中默认的对⻬数为8
 //结构体总⼤⼩为：最⼤对⻬数（所有变量类型最⼤者与默认对⻬参数取最⼩）的整数倍。
// 如果嵌套了结构体的情况，嵌套的结构体对⻬到⾃⼰的最⼤对⻬数的整数倍处，结构体的整体⼤⼩
//就是所有最⼤对⻬数（含嵌套结构体的对⻬数）的整数倍


// 计算⼀下A/B/C实例化的对象是多⼤？
//class A
//{
//	
//public :
//	void Print()
//	{
//		cout << _ch << endl;
//	}
//private:
//	char _ch;
//	int _i; 
//};
//class B
//{
//	
//public :
//	void Print()
//	{
//		//...
//	}
//};
//class C
//{};
//
//
///// <summary>
/////  类中没有成员变量时 大小 为: 1
///// </summary>
///// <returns></returns>
//int main()
//{
//	A a;
//	B b;
//	C c;
//	cout << sizeof(a) << endl;
//
//	cout << sizeof(b) << endl;
//	cout << sizeof(c) << endl;
//	return 0;
//}



///////////////////////////////////////  重要题目 : 为什么要有内存对齐 ?  /////////////////////////////////

// 首先硬件的设计并不是在随意位置读取随意个数的字节 , 而是根据硬件的不同会让其在固定位置一次读固定大小
// 这些跟硬件的设计有关 
// 那么内存对齐可以有什么优化: 提高效率 , 虽然有空间浪费 ,但是效率大大提高 (用空间换时间)

//例如:从偏移量为0 位置开始 ,一次读 4 个字节
//若内存中存储的是 : char , int 
//要访问 int 的4个字节 : 那么开始读先读 char 的一个字节 ,再读 3 个字节(int) , 再接着读 1 个字节 , 再拼接 前面的3个,这样效率就很低下了


//// 故 : 内存对齐原则就显的很有用了 


//////////////////////////////////////////  两个经典例题对比


// eg 1 :


// 以下程序会发生什么情况 : 
//#include <iostream>
//using namespace std;
//
//class A
//{
//public:
//	void Print()
//	{
//		cout << "A::Print()" << endl;
//	}
//private:
//	int _a;
//};
//
//int main()
//{
//	//首先类成员函数中有 this 指针 , 函数指针是不存在对象中的 , 是存在一个公共区域 ,所有转到反汇编
//	
//	A* p = nullptr;
//	// p 就是类 A 的对象
//	// 访问对象的成员函数 ,成员函数中是有 this 指针的 
//	// 1. 传 this 指针  - >  反汇编 :  mov   this 地址
//	// 2. call  地址跳转指定函数 , 执行指定代码段
//
//	// 但观看函数 Print 中 , 虽然 this 指针是空指针 , 但没有使用该空指针 , 所以程序会正常运行
//	p->Print();
//	return 0;
//}

// eg 2 :

//
//#include <iostream>
//using namespace std;
//
//class A
//{
//public:
//	void Print()
//	{
//		cout << "A::Print()" << endl;
//		cout << _a << endl; // 访问 _a 
//	}
//private:
//	int _a;
//};
//
//int main()
//{
//	//首先类成员函数中有 this 指针 , 函数指针是不存在对象中的 , 是存在一个公共区域 ,所有转到反汇编
//
//	A* p = nullptr;
//	// p 就是类 A 的对象
//	// 访问对象的成员函数 ,成员函数中是有 this 指针的 
//	// 1. 传 this 指针  - >  反汇编 :  mov   this 地址
//	// 2. call  地址跳转指定函数 , 执行指定代码段
//
//	// 但观看函数 Print 中 ,  this 指针是空指针 , 使用该空指针 , 所以程序会崩溃
//	p->Print();
//	return 0;
//
//}


//// 类的成员函数底层是先 传 this 指针 , call 一个地址跳转到  jump 指令 , 最终跳转到执行函数代码块





////////////////////////////////////////////////////////  类的默认成员函数  //////////////////////////////////
// 没有显示实现时 , 编译器会自动生成的成员函数 , 一般情况下要我们显示实现 , 一般情况编译器自动生成的不符合我们要求


// 构造函数 : 完成初始化的
// 析构函数 : 完成资源的清理工作 ->  完成初始化和清理工作


//构造函数 - > 是类的默认成员函数的一种
//  负责 : 初始化工作 , 对成员变量初始化

// 1. 构造函数名与类名相同
// 2. 无返回值 ,啥也不用写
// 3. 构造函数可以函数重载
// 4. 在对象实例化时 , 编译器会自动调用构造函数 (变化最大的一点)
// 5. 默认构造函数 : 无参构造函数 , 全缺省构造函数 , 编译器自动生成的函数 都叫:默认构造函数
//    注意: 无参构造函数 \  全缺省构造函数 \ 编译器自动生成的函数 有且只有一个存在
//    因为: 显示定义构造函数 ,编译器就不会自动生成构造函数了 , 而且 无参构造函数 \  全缺省构造函数 两个只能出现一个 ,因为调用有歧义
// 6. 对于编译器自动生成的函数 : 其对内置类型 (int , char ... 等)的初始化没有要求 , 但对自定义类型 , 必须调用它的默认构造函数 ,没有就会报错
//  这里特别注意 : 编译器自动生成的函数 -> 对于自定义类型必须调用它的默认构造 ~~

//#include <iostream>
//
//class Date
//{
//public:
	// 对类的成员函数会有一个隐含的 this 指针
	//无参构造函数
	/*Date()
	{
		_year = 0;
		_month = 0;
		_day = 0;
	}*/


	// 无参的和有参的构成函数重载

	//Date(int year , int month , int day)
	//{
	//	_year = year;
	//	_month = month;
	//	_day = day;
	//}


	//全缺省构造
//	Date(int year = 0, int month = 0, int day = 0 )
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	void Print()
//	{
//		cout << _year << " / " << _month << " / " << _day << endl;
//	}
//
//private:
//	//类的成员变量 , 这里是声明 , 没有开空间
//	int _year;
//	int _month;
//	int _day;
//};
//
//int main()
//{
//	// 无参构造需这样写
//	//Date d1;
//	//d1.Print();
//
//	//带参默认构造
//	/*Date d2(2014, 10, 15);
//	d2.Print();*/
//
//	// 全缺省
//	Date d3;
//	d3.Print();
//
//	Date d4(2024);
//	d4.Print();
//
//	Date d5(2024, 10);
//	d5.Print();
//
//	Date d6(2024, 10, 15);
//	d6.Print();
//
//	return 0;
//}

///// 根据以上可以再次理解默认构造 : 只要不传参数就可以的构造函数就叫做默认构造函数


/////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 那么对于编译器自动生成的函数 :
// 两个栈实现队列便符合

//typedef int STDataType;
//
//class Stack
//{
//public:
//
//	//构造函数 (初始化)
//
//	//若自定义类型没有默认构造 , 那么就会报错
//	//无法引用 "MyQuee" 的默认构造函数 -- 它是已删除的函数
//
//	/*Stack(int n)
//	{
//		_a = (STDataType*)malloc(sizeof(STDataType) * n);
//		if (nullptr == _a)
//		{
//			perror("malloc fail !");
//			return;
//		}
//		_capacity = n;
//		_top = 0;
//	}*/
//
//	Stack(int n = 4)
//	{
//		_a = (STDataType*)malloc(sizeof(STDataType) * n);
//		if (nullptr == _a)
//		{
//			perror("malloc fail !");
//			return;
//		}
//		_capacity = n;
//		_top = 0;
//	}
//	
//private:
//	STDataType* _a;
//	size_t _capacity;
//	size_t _top;
//};
//
//
//
//class MyQuee
//{
//
//public:
//	void Print()
//	{
//		//cout << _size << endl;
//	}
//private:
//	Stack pushst;
//	Stack popst;
//	//int _size;
//};
//
//
//int main()
//{
//	// 这里的 MyQuee 没有显示实现构造函数 , 在对象实例化的同时,编译器会自动生成构造函数
//	MyQuee mq;
//	//mq.Print();
//
//	return 0;
//
//}


/////////////////////////////////////////////////////////////////////////

//析构函数 - > 完成类的对象的资源清理工作
// C++ 中规定 : 对象在销毁时会自动调用析构函数

// 理解:
// 1. 首先析构函数是用来清理资源的 , 也就是说没有资源申请的对象就不需要析构函数
// 2. 析构函数与构造函数作用正好相反
// 3. 析构函数的函数名 :  类名前 + ~ (取反符)
// 4. 析构函数无返回值 , 什么也不需要写
// 5. 析构函数有且只有一个 (与构造函数不同)
// 6. 在对象的声明周期结束时, 会自动调用析构函数
// 7. 未显示定义 , 系统会自动生成默认析构函数
// 8. 我们不写自动生成的析构函数 : 1. 对内置类型也没有规定 2.对自定义类型会调用它的析构函数
// 9. 对于自定义类型即使显示写了析构 ,还会调用自定义成员的析构 , 也就是说: 自定义类型无论什么情况都会调用析构


//#include <iostream>
//
//typedef int STDataType;
//
//class Stack
//{
//public:
//
//	Stack(int n = 4)
//	{
//		_a = (STDataType*)malloc(sizeof(STDataType) * n);
//		if (nullptr == _a)
//		{
//			perror("malloc fail !");
//			return;
//		}
//		_capacity = n;
//		_top = 0;
//	}
//
//	//析构函数 - > 析构函数只能有一个
//	~Stack()
//	{
//		free(_a);
//		_a = NULL;
//		_capacity = _top = 0;
//	}
//
//private:
//	STDataType* _a;
//	size_t _capacity;
//	size_t _top;
//};
//
//int main()
//{
//	//对象实例化时 , 会自动调用构造
//	Stack st;
//	//对象生命周期结束时 , 会自动调用析构
//	
//	return 0;
//
//}


//////////////////////// 不显示写析构时,会自动生成默认析构,默认析构对内置类型不做处理 ,但对自定义类型会调用它的析构

///////////////////  两个栈实现队列

//class MyQuee
//{
//public:
//	//没有实现构造和析构, 都会自动生成 , 自动生成的会调用  Stack 的默认构造和默认析构
//
//	//若显示实现了析构 , 还会调用 Stack 的析构
//	~MyQuee()
//	{
//
//	}
//
//private:
//	Stack pushst;
//	Stack popst;
// 
// // 这里注意析构是 :  先析构后定义的 
//};
//
//
//int main()
//{
//	MyQuee mq;
//	return 0;
//}


/////////////////////////////////////////////////////////////////////////////////////////////////////

// 对比 : 构造函数 和 析构函数

// 相同点 :
// 1. 构造函数 和 析构函数 都是类的默认成员函数(我们不写时编译器会自动生成的函数)
// 2. 都对内置类型不做处理 , 对自定类型会调用相应的构造(这里的构造指:默认构造) 和 析构
// 3. 都无反回值 , 什么都不需要写
// 4. 都会进行自动调用 (最大特点) {构造函数在类的对象实例化时自动调用 , 析构函数在类的对象生命周期结束时自动调用}
// 5. 显示生成就不在会自动生成相应的构造和析构了 , 不显示都会自动生成{构造函数会自动生成一个无参的默认构造函数,析构函数会自动生成一个默认的析构}



// 不同点 : 
// 1. 函数名不同 : 构造函数名与类名相同 , 析构函数名在类名前 + (~)符号
// 2. 构造函数是进行对象初始化工作 , 析构函数是进行对象的资源清理工作
// 3. 构造函数可以重载 , 析构函数有且只有一个

//////////////////// 特别注意:  对于自动生成的例子: MyQuee(两个栈实现队列)中,不需要实现 构造 和 析构 
////////////////////                            对于日期类没有资源的指向,就不需要析构了 



/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 拷贝构造函数是特殊的构造函数
// 1. 第一个参数必须是自身类对象的引用 , 其余参数必须有默认值(缺省值)
// 2. 拷贝构造函数可以重载
// 3. 函数名与类名相同
// 4. 无返回值 ,什么也不用写
// 5. 第一个参数必须是引用传参 , 不能是传值传参 , 因为 C++ 规定对自定义类型的传值传参要调用拷贝构造 , 传值传参会形成无穷递归
// 6. 若为想显示定义拷贝构造 , 系统会自动生成拷贝构造函数 ,自动生成的对 内置类型完成 [值拷贝(浅拷贝))] ,对自定义类型会调用它的拷贝构造


/// <summary>
/// 
///  C++ 规定 : 对自定义类型对象的传值传参 , 传值返回都要调用拷贝构造
///  
///  
///  自动生成的拷贝构造对内置类型也有处理 : 对内置类型完成 浅拷贝(值拷贝) -> 也就是一个字节一个字节拷贝
/// 
/// </summary>

//class Date
//{
//public:
//	Date(int year = 1 , int month = 1 , int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	void Print()
//	{
//		cout << _year << " / " << _month << " / " << _day << endl;
//	}
//
//	// err , 第一个参数必须是自身类对象的引用
//	// 报错:
//	// 类 "Date" 的复制构造函数不能带有 "Date" 类型的参数	
//	/*Date(Date d)
//	{
//		_year = d._year;
//		_month = d._month;
//		_day = d._day;
//	}*/
//
//	//不显示写时,会自动生成
//
//	//Date(Date& d)
//	//{
//	//	_year = d._year;
//	//	_month = d._month;
//	//	_day = d._day;
//	//}
//
//private:
//	int _year;
//	int _month;
//	int _day;
//};
//
////以下函数打印一下传过来的对象的内容
//void Fun1(Date d)
//{
//	d.Print();
//}
//
//
////自定义类型传参传引用减少拷贝
//void Fun2(const Date& d) // 一般加上 const 外面不可改变
//{
//	d.Print();
//}
//
//
//
//int main()
//{
//
//	Date d1(2024,10,16);
//	d1.Print();
//
//	//C++ 中规定对自定义类型的拷贝必须调用拷贝构造
//	//用法就和函数一样
//	Date d2(d1);
//	d2.Print();
//
//	// 以下写法也是拷贝构造
//	Date d3 = d1;
//	d3.Print();
//
//	//因为对于自定义类型的传值传参要调用拷贝构造,以下给出例子
//	Fun1(d1);// 通过调试可以看出会先调用拷贝构造 , 在会走 Fun1() 函数
//
//	// 所以建议对于自定义类型的传值传参用 传引用 , 这样减少拷贝 - > 即 : Fun2() 函数
//	Fun2(d1);// 通过调试可以看出 Fun2() 函数会直接调用 , 不会走拷贝构造
// 
//
//	//假如不显示写拷贝构造,会自动生成拷贝构造, 对内置类型完成浅拷贝
//
//	Date d4(2024, 2, 2);
//
//	//自定义对象的拷贝 ,但类中没有显示实现, 会自动生成,会完成浅拷贝
//	Date d5(d4);
//	d5.Print();
//
//	return 0;
//}


////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 拷贝构造再次理解

// 拷贝构造 - > 其是特殊的构造函数 , 故 : 其作用还是进行初始化工作 , 只不过这个初始化是用一个对象初始化另一个对象

// C++ 中规定 : 自定义类型对象的拷贝必须调用其拷贝构造 !!!!!!
// 
// 拷贝构造特点 :
// 1. 拷贝构造函数名与类名相同
// 2. 拷贝构造函数的第一个参数必须是自身类对象的引用 , 其余额外的参数必须有缺省值(默认值) , 注意: 第一个参数不能是传值,因为会造成无限递归
//    // 这里因为 C++ 中规定传值传参必须调用拷贝构造 , 但拷贝构造函数的第一个参数是传值的话
//	  // 有以下情况 : 配图 ~ 
// 3. 拷贝构造函数可以重载
// 4. 若为显示定义 , 编译器就会自动生成一个拷贝构造 , 自动生成的对内置类型完成浅拷贝(值拷贝)(一个字节一个字节拷贝) , 对自定义类型会调用它的拷贝构造
// 
// 
//



//class Date 
//{
//public:
//
//	//构造
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	//拷贝构造
//	/*Date(Date& d)
//	{
//		_year = d._year;
//		_month = d._month;
//		_day = d._day;
//	}*/
//
//	void Print()
//	{
//		cout << _year << " / " << _month << " / " << _day << endl;
//	}
////private:
//	int _year;
//	int _month;
//	int _day;
//};



//对于自定义类型的传参传值 -> 会多调用一次拷贝构造
//void Func1(Date d)
//{
//	d.Print();
//}
//
//void Func2(Date& d)
//{
//	d.Print();
//}
//
//int main()
//{
//	Date d1(2024,10,18);
//	d1.Print();
//
//	//对象的拷贝必须调用拷贝构造调用拷贝构造
//	Date d2(d1); // 将 d1 拷贝给 d2 , 其实就是对 d2 进行初始化
//	d2.Print();
//
//	//// 这里给一个函数体现拷贝构造
//	Func1(d2);
//	Func2(d2);
//	return 0;
//}

////////////////////////////////// 编译器自动生成

//
//int main()
//{
//	Date d1(2024, 10, 18);
//	d1.Print();
//	
//	//对象的拷贝必须调用拷贝构造调用拷贝构造 , 但没有显示实现,编译器自动生成完成浅拷贝
//	Date d2(d1); // 将 d1 拷贝给 d2 , 其实就是对 d2 进行初始化
//	d2.Print();
//
//
//	return 0;
//}



/////////////////////////////// 总结 : 
//  编译器自动生成的拷贝构造虽然可以完成浅拷贝 ,但是对于对象指向资源的就不行了 , 像: Stack类我们就要实现深拷贝, 对于对象没有指向资源的, 像: Date 类
// 对象没有指向什么资源 ,就可以使用自动生成的拷贝构造


////// 技巧: 看类中是否实现了 析构 , 若实现了析构,就要实现拷贝构造 ~~
//// 传引用建议+ const 




//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 运算符重载 
// 在对象使用运算符时 , C++ 规定必须使用运算符重载 , 运算符重载是赋予一个运算符新的含义
// 运算符重载 - > 是一个函数 , 有以下特点 : 
// 1. 函数名为 operator + 运算符
// 2. 可以有返回值和参数
// 3. 必须有一个类类型的形参
// 4. 特别注意: 有 5 个运算符不能重载: .* , :: , sizeof , ?:(三目) , .(点操作符)
// 这里记忆一下重载 >> << 要重载成全局的 , 因为类中的 this 会占用 对象

//class Date
//{
//public:
//
//	//构造
//	Date(int year = 1, int month = 1, int day = 1)
//	{
//		_year = year;
//		_month = month;
//		_day = day;
//	}
//
//	//拷贝构造
//	/*Date(Date& d)
//	{
//		_year = d._year;
//		_month = d._month;
//		_day = d._day;
//	}*/
//
//
//	//重载成类的成员函数就要少传一个参数 , 因为 : 第一个参数是隐含的 this 指针
//	bool operator==(const Date& d2)
//	{
//		return _year == d2._year
//			&& _month == d2._month
//			&& _day == d2._day;
//	}
//
//	// 重载 ++ 
//	Date operator++()
//	{
//		cout << "前置++" << endl;
//		return *this;
//	}
//
//	// C++ 中规定重载后置++ 要多一个int 参数
//	Date operator++(int) 
//	{
//		cout << "后置++" << endl;
//		return *this;
//
//	}
//
//	void Print()
//	{
//		cout << _year << " / " << _month << " / " << _day << endl;
//	}
//private:
//	int _year;
//	int _month;
//	int _day;
//};



// 重载 + 

//重载全局 
//缺点就是不能访问类的成员函数 , 因为类的成员函数是私有的
//bool operator==(const Date& d1 , const Date& d2)
//{
//	return d1._year == d1._year
//		&& d1._month == d2._month
//		&& d1._day == d2._day;
//}

// 对以上解决办法有以下
// 1. 成员放公有(不推荐)
// 2. Date 提供 getxx函数
// 3. 友元函数
// 4. 重载成类的成员函数(推荐)
//
//int main()
//{
//
//	//运算符重载的使用
//	Date d1(2024, 10, 18);
//
//	Date d2 = d1;
//
//	///// 这里注意对于 二元操作符 , 左边传第一个参数 , 右边传第二个参数
//	//d1 == d2;
//	// 这样写会自动转化成 operator==(d1,d2);
//
//
//	//若重载成类的成员函数又有所不同 , 因为类的成员函数第一个参数是隐含的 this 指针
//
//	d1 == d2;
//	// 会自动转 : operator(&d1 , d2);
//
//	return 0;
//}

////////////////////////////////////////////////////////////////////////////////////////////////////////////

// 赋值运算符重载

// 1. 赋值运算符重载函数是类的默认成员函数
// 2. 作用是 : 对两个已经创造的对象的直接赋值. , 注意是 : 两个已经创造
//    这里对比构造函数 , 构造函数是一个对象初始化一个对象 , 也就是说只有一个对象已经创建

// 3. 赋值运算符重载必须重载成类的成员函数 
// 4. 没有显示实现时 , 编译器会自动生成一个赋值运算符重载函数 , 这个函数对内置类型会完成浅拷贝 , 与拷贝构造函数一样, 对自定义类型会调用它的拷贝
// 5.  还是和拷贝构造函数一样 , 对于没有指向资源的 ,像: Date 这样的类,可以使用自动生成的完成浅拷贝 , 但对于指向资源的 , 像: Stack 就要手动实现深拷贝了
//     像 MyQuee 这样的类 , 可以调用 Stack 的赋值运算符重载函数 , 也不需要自己实现
//  // 技巧 :  看有没有析构 , 有析构就实现赋值运算符重载函数 
//


// 

class Date
{
public:

	//构造
	Date(int year = 1, int month = 1, int day = 1)
	{
		_year = year;
		_month = month;
		_day = day;
	}

	/*Date(Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;
	}*/


	//赋值运算符重载函数必须重载成类的成员函数
	// 传参可以传值 , 这个就和函数的使用是一样的, 传值的话会调用一次拷贝构造 ,然后才会调用一该函数
	// 建议传引用 , 减少拷贝 

	//有返回值的场景是支持连续赋值
	Date& operator=(const Date& d)
	{
		_year = d._year;
		_month = d._month;
		_day = d._day;

		return *this;
	}

	void Print()
	{
		cout << _year << " / " << _month << " / " << _day << endl;
	}
private:
	int _year;
	int _month;
	int _day;
};


int main()
{
	Date d1(2024, 10, 18);
	d1.Print();

	Date d2(2024);
	//Date d2(d1); // 拷贝构造函数

	// 这里重点区分
	//这是拷贝构造
	Date d = d1; // d 来初始化 d2 

	d1 = d2;  // 赋值运算符重载 , d1 和 d2 已经存在

	d1.Print();

	Date d4;

	// d4 = d2 = d1;
	d4 = d2 = d1;
	d4.Print();
	d2.Print();
	d1.Print();
	return 0;

}